﻿/*  
	========================================================================
	 awzBoxes Library
	
	 Homepage: http://www.awzhome.de/
	========================================================================
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of awzBoxes Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2011-2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	========================================================================
*/

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Markup;
using System;
using System.Windows.Data;
using System.Linq;

namespace AWZhome.awzBoxes.Tools
{
	/// <summary>
	/// Grid control that arranges child controls as rows (i.e. vertically) and automatically
	/// inserts GridSplitters between them.
	/// </summary>
	[ContentProperty("Children")]
	public partial class SplittedRowGrid : UserControl
	{
		private const double RowMinHeight = 100;
		private List<GridSplitter> _gridSplitters = new List<GridSplitter>();

		/// <summary>
		/// Creates a new <see cref="AWZhome.awzBoxes.Tools.SplittedRowGrid"/>.
		/// </summary>
		public SplittedRowGrid()
		{
			this.Children = new ObservableCollection<FrameworkElement>();
			this.Children.CollectionChanged += Children_CollectionChanged;
			InitializeComponent();
		}

		/// <summary>
		/// If set to <c>true</c>, layout is adjusted when new item is inserted.
		/// </summary>
		public bool AdjustSizesOnInsert
		{
			get;
			set;
		}

		/// <summary>
		/// Returns the collection of the children controls arranged in the grid.
		/// </summary>
		public ObservableCollection<FrameworkElement> Children
		{
			get;
			private set;
		}

		/// <summary>
		/// Sets the width of a specific row.
		/// </summary>
		/// <param name="index">Index of the grid row.</param>
		/// <param name="height">Row height to set.</param>
		public void SetRowHeight(int index, double height)
		{
			this.SetRowHeight(index, height, false);
		}

		/// <summary>
		/// Sets the width of a specific row.
		/// </summary>
		/// <param name="index">Index of the grid row.</param>
		/// <param name="height">Row height to set.</param>
		/// <param name="force">If <c>true</c>, enforces the setting.</param>
		public void SetRowHeight(int index, double height, bool force)
		{
			if ((index >= 0) && (index < this.InnerGrid.RowDefinitions.Count))
			{
				if (!Double.IsNaN(height))
				{
					// Set relative height
					this.InnerGrid.RowDefinitions[index].Height = new GridLength(height, GridUnitType.Star);
				}
			}
		}

		/// <summary>
		/// Retrieves the height of a specific row.
		/// </summary>
		/// <param name="index">Index of the grid row.</param>
		/// <param name="actualHeight">If <c>true</c>, returns the actual height of the row.</param>
		/// <returns>
		/// The row height, <see cref="System.Double.NaN"/> for expansion
		/// or <c>null</c> if <paramref name="index"/> was invalid.
		/// </returns>
		public double? GetRowHeight(int index, bool actualHeight)
		{
			if ((index >= 0) && (index < this.InnerGrid.RowDefinitions.Count))
			{
				//if (actualHeight)
				//{
				//    return this.InnerGrid.RowDefinitions[index].ActualHeight;
				//}
				//else
				//{
				GridLength gridLength = this.InnerGrid.RowDefinitions[index].Height;
				if (gridLength != null)
				{
					return gridLength.Value;
				}
				else
				{
					return null;
				}
				//}
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Summarizes all row sizes and returns the result.
		/// </summary>
		/// <returns>Sum of all column sizes.</returns>
		public double GetRowSizes()
		{
			return this.InnerGrid.RowDefinitions.Sum(e => e.Height.Value);
		}

		/// <summary>
		/// Handles changes on the Children collection.
		/// </summary>
		/// <param name="sender">Event sender.</param>
		/// <param name="e">Event data.</param>
		private void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			// Update Grid rows with splitters, when Children property changes
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					FrameworkElement elem = (FrameworkElement) e.NewItems[0];

					RowDefinition newRowDefinition = new RowDefinition();
					if (this.AdjustSizesOnInsert)
					{
						// Divide the space between the new and the neighbor child
						if (this.InnerGrid.RowDefinitions.Count > 0)
						{
							if (e.NewStartingIndex == 0)
							{
								newRowDefinition.Height = new GridLength(this.InnerGrid.RowDefinitions[0].Height.Value / 2, GridUnitType.Star);
								this.InnerGrid.RowDefinitions[0].Height = newRowDefinition.Height;
							}
							else
							{
								newRowDefinition.Height =
									new GridLength(this.InnerGrid.RowDefinitions[e.NewStartingIndex - 1].Height.Value / 2, GridUnitType.Star);
								this.InnerGrid.RowDefinitions[e.NewStartingIndex - 1].Height = newRowDefinition.Height;
							}
						}
					}
					this.InnerGrid.RowDefinitions.Insert(e.NewStartingIndex, newRowDefinition);

					// Create the GridSplitter
					GridSplitter newGridSplitter = new GridSplitter();
					newGridSplitter.Height = 5;
					newGridSplitter.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
					newGridSplitter.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
					newGridSplitter.DragCompleted += newGridSplitter_DragCompleted;
					Grid.SetRow(newGridSplitter, e.NewStartingIndex);
					_gridSplitters.Insert(e.NewStartingIndex, newGridSplitter);

					Thickness margin = elem.Margin;
					margin.Bottom = newGridSplitter.Height;
					elem.Margin = margin;

					Grid.SetRow(elem, e.NewStartingIndex);

					this.InnerGrid.Children.Add(elem);
					this.InnerGrid.Children.Add(newGridSplitter);

					this.UpdateGridRows();
					break;

				case NotifyCollectionChangedAction.Move:
					break;

				case NotifyCollectionChangedAction.Remove:
					FrameworkElement oldElem = (FrameworkElement) e.OldItems[0];
					if (e.OldStartingIndex < _gridSplitters.Count)
					{
						BindingOperations.ClearBinding(_gridSplitters[e.OldStartingIndex],
							GridSplitter.VisibilityProperty);
						this.InnerGrid.Children.Remove(_gridSplitters[e.OldStartingIndex]);
						_gridSplitters.RemoveAt(e.OldStartingIndex);
					}
					if (e.OldStartingIndex < this.InnerGrid.RowDefinitions.Count)
					{
						this.InnerGrid.RowDefinitions.RemoveAt(e.OldStartingIndex);
					}

					this.InnerGrid.Children.Remove(oldElem);

					this.UpdateGridRows();
					break;

				case NotifyCollectionChangedAction.Replace:
					break;

				case NotifyCollectionChangedAction.Reset:
					// Resetting the grid
					this.InnerGrid.Children.Clear();
					this.InnerGrid.RowDefinitions.Clear();
					_gridSplitters.Clear();
					break;

				default:
					break;
			}
		}

		private void newGridSplitter_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
		{
			GridSplitter gridSplitter = sender as GridSplitter;
			if (gridSplitter != null)
			{
				int splitterIndex = _gridSplitters.IndexOf(gridSplitter);
				if ((splitterIndex > -1) && (gridSplitter.Visibility == System.Windows.Visibility.Visible))
				{
					// Found this GridSplitter in list -> restore its size, if min. size has been underrun
					FrameworkElement prevChild = this.Children[splitterIndex];
					if (prevChild.ActualHeight <= RowMinHeight)
					{
						double? prevSize = this.GetRowHeight(splitterIndex + 1, false);
						if (prevSize.HasValue)
						{
							this.SetRowHeight(splitterIndex, prevSize.Value / 2);
						}
					}
					FrameworkElement nextChild = this.Children[splitterIndex + 1];
					if (nextChild.ActualHeight <= RowMinHeight)
					{
						double? nextSize = this.GetRowHeight(splitterIndex, false);
						if (nextSize.HasValue)
						{
							this.SetRowHeight(splitterIndex + 1, nextSize.Value / 2);
						}
					}
				}
			}
		}

		/// <summary>
		/// Updates the grid settings of all client controls.
		/// </summary>
		private void UpdateGridRows()
		{
			// Run through all elements and update their rows
			for (int i = 0; i < this.Children.Count; i++)
			{
				Grid.SetRow(this.Children[i], i);
			}
			for (int i = 0; i < _gridSplitters.Count; i++)
			{
				GridSplitter gridSplitter = _gridSplitters[i];
				Grid.SetRow(gridSplitter, i);
				if (i == this.Children.Count - 1)
				{
					// The last grid splitter should be hidden
					gridSplitter.Visibility = Visibility.Collapsed;
					// Hide child control margin
					Thickness margin = this.Children[i].Margin;
					margin.Bottom = 0;
					this.Children[i].Margin = margin;
				}
				else
				{
					gridSplitter.Visibility = Visibility.Visible;
					// Show child control margin
					Thickness margin = this.Children[i].Margin;
					margin.Bottom = gridSplitter.Height;
					this.Children[i].Margin = margin;
				}
			}
		}
	}
}
